﻿using System;
using System.Configuration;
using System.Reflection;
using SimpleServiceBus.Bus.MessageManagement;
using SimpleServiceBus.Bus.Pipeline;
using SimpleServiceBus.Bus.Transport;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Endpoint.Management.Monitoring.Profiling;
using SimpleServiceBus.Endpoint.Metadata;
using SimpleServiceBus.Utilities.ExpressionEval;
using SimpleServiceBus.Utilities.ObjectBuilder;

namespace SimpleServiceBus.Configuration
{
    public class EndpointConfiguration : ConfigurationSection
        //where TRansportType : class, ITransport
    {
        [ConfigurationProperty("endpointId", IsRequired = true)]
        public string EndpointID
        {
            get { return (string) base["endpointId"]; }
            set { base["endpointId"] = value; }
        }

        [ConfigurationProperty("name", IsRequired = false)]
        public string Name
        {
            get { return (string) base["name"]; }
            set { base["name"] = value; }
        }


        [ConfigurationProperty("description", DefaultValue = "", IsRequired = false)]
        public string Description
        {
            get { return (string) base["description"]; }
            set { base["description"] = value; }
        }


        [ConfigurationProperty("returnAddress", IsRequired = false)]
        public string ReturnAddress
        {
            get { return (string) base["returnAddress"]; }
            set { base["returnAddress"] = value; }
        }


        [ConfigurationProperty("monitorServiceAddress")]
        public string MonitorServiceAddress
        {
            get { return (string)base["monitorServiceAddress"]; }
            set { base["monitorServiceAddress"] = value; }
        }


        [ConfigurationProperty("techSupportEmailAddress", DefaultValue = " ", IsRequired = false)]
        public string SupportEmailAddress
        {
            get { return (string) base["techSupportEmailAddress"]; }
            set { base["techSupportEmailAddress"] = value; }
        }

        [ConfigurationProperty("distributorAddress")]
        public string DistributorAddress
        {
            get { return (string)base["distributorAddress"]; }
            set { base["distributorAddress"] = value; }
        }

        [ConfigurationProperty("distributorControlAddress")]
        public string DistributorControlAddress
        {
            get { return (string)base["distributorControlAddress"]; }
            set { base["distributorControlAddress"] = value; }
        }

        [ConfigurationProperty("poisonMessageAddress")]
        public string PoisonMessageAddress
        {
            get { return (string)base["poisonMessageAddress"]; }
            set { base["poisonMessageAddress"] = value; }
        }

        [ConfigurationProperty("maxMessageRetryCount",IsRequired=false,DefaultValue = 3)]
        public int MaxMessageRetryCount
        {
            get { return (int) base["maxMessageRetryCount"]; }
            set { base["maxMessageRetryCount"] = value;}
        }

        [ConfigurationProperty("techSupportTelephoneNumber", DefaultValue = "", IsRequired = false)]
        public string SupportTelephoneNumber
        {
            get { return (string) base["techSupportTelephoneNumber"]; }
            set { base["techSupportTelephoneNumber"] = value; }
        }


        [ConfigurationProperty("latitude", IsRequired = false)]
        public double EndpointLatitude
        {
            get { return (double) base["latitude"]; }
            set { base["latitude"] = value; }
        }


        [ConfigurationProperty("longitude", IsRequired = false)]
        public double EndpointLongitude
        {
            get { return (double) base["longitude"]; }
            set { base["longitude"] = value; }
        }

        [ConfigurationProperty("statusReportInterval")]
        public TimeSpan StatusReportInterval
        {
            get { return (TimeSpan)base["statusReportInterval"]; }
            set { base["statusReportInterval"] = value; }
        }

        [ConfigurationProperty("statusReportGracePeriod")]
        public TimeSpan StatusReportGracePeriod
        {
            get { return (TimeSpan)base["statusReportGracePeriod"]; }
            set { base["statusReportGracePeriod"] = value; }
        }

        [ConfigurationProperty("incomingMessageLoggingLevel")]
        public MessageLoggingLevel IncomingMessageLoggingLevel
        {
            get { return (MessageLoggingLevel) base["incomingMessageLoggingLevel"]; }
            set { base["incomingMessageLoggingLevel"] = value;}
        }

        [ConfigurationProperty("outgoingMessageLoggingLevel")]
        public MessageLoggingLevel OutgoingMessageLoggingLevel
        {
            get { return (MessageLoggingLevel)base["outgoingMessageLoggingLevel"]; }
            set { base["outgoingMessageLoggingLevel"] = value; }
        }
     
        [ConfigurationProperty("statusProbes")]
        public EndpointStatusProbeConfigurationCollection StatusProbes
        {
            get { return (EndpointStatusProbeConfigurationCollection)base["statusProbes"]; }
            set { base["statusProbes"] = value; }
        }

        [ConfigurationProperty("messageHandlerAssemblies", IsRequired = false)]
        public AssemblyConfigurationCollection MessageHandlerAssemblies
        {
            get { return (AssemblyConfigurationCollection) base["messageHandlerAssemblies"]; }
            set { base["messageHandlerAssemblies"] = value; }
        }

        [ConfigurationProperty("messageAssemblies", IsRequired = false)]
        public MessageAssemblyConfigurationCollection MessageAssemblies
        {
            get { return (MessageAssemblyConfigurationCollection)base["messageAssemblies"]; }
            set { base["messageAssemblies"] = value; }
        }

        [ConfigurationProperty("messageFilters")]
        public MessageFilterConfigurationCollection MessageFilters
        {
            get { return (MessageFilterConfigurationCollection)base["messageFilters"]; }
            set { base["messageFilters"] = value; }
        }

        [ConfigurationProperty("numberOfWorkerThreads", IsRequired = false, DefaultValue=5)]
        public int NumberOfWorkerThreads
        {
            get { return (int)this["numberOfWorkerThreads"]; }
            set { this["numberOfWorkerThreads"] = value; }
        }

        [ConfigurationProperty("maxRetries", IsRequired = false, DefaultValue=5)]
        public int MaxRetries
        {
            get { return (int)this["maxRetries"]; }
            set { this["maxRetries"] = value; }
        }

        [ConfigurationProperty("sendOnly",IsRequired=false,DefaultValue=false)]
        public bool SendOnly
        {
            get { return (bool) this["sendOnly"]; }
            set { this["sendOnly"] = value;}
        }

        [ConfigurationProperty("profiler",IsRequired=false)]
        public PipelineProfilerConfigurationElement Profiler
        {
            get { return (PipelineProfilerConfigurationElement) this["profiler"]; }
            set { base["profiler"] = value; }
        }

        public void CopyTo(EndpointDescription ep)
        {
            ep.EndpointID = EndpointID;
            if (!String.IsNullOrEmpty(Name)) ep.Name = Name;
            if (!String.IsNullOrEmpty(Description)) ep.Description = Description;
            if (!String.IsNullOrEmpty(ReturnAddress)) ep.ReturnAddress = ReturnAddress;
            if (!String.IsNullOrEmpty(MonitorServiceAddress)) ep.MonitorServiceAddress = MonitorServiceAddress;
            if (!String.IsNullOrEmpty(DistributorAddress)) ep.DistributorAddress = DistributorAddress;
            if (!String.IsNullOrEmpty(DistributorControlAddress)) ep.DistributorControlAddress = DistributorControlAddress;
            if (!String.IsNullOrEmpty(PoisonMessageAddress)) ep.PoisonMessageAddress = PoisonMessageAddress;
            if (MaxMessageRetryCount != 0) ep.MaxMessageRetryCount = MaxMessageRetryCount;
            if (!String.IsNullOrEmpty(SupportEmailAddress)) ep.SupportEmailAddress = SupportEmailAddress;
            if (!String.IsNullOrEmpty(SupportTelephoneNumber)) ep.SupportTelephoneNumber = SupportTelephoneNumber;
            if (EndpointLatitude != 0.0) ep.EndpointLatitude = EndpointLatitude;
            if (EndpointLongitude != 0.0) ep.EndpointLongitude = EndpointLongitude;
            ep.IncomingMessageLogLevel = IncomingMessageLoggingLevel;
            ep.OutgoingMessageLogLevel = OutgoingMessageLoggingLevel;
            ep.SendOnly = SendOnly;
        }

        public IEndpointDescription ConfigureEndpointDescription(EndpointDescription desc)
        {
            if (desc == null)
                desc = new EndpointDescription();
            CopyTo(desc);
            return desc;
        }

        public virtual void Configure<TransportType>(IEndpointBuilder endpoint)
            where TransportType : class, ITransport
        {
            if (!endpoint.IsBuilt)
                throw new ArgumentException(
                    "An endpoint must be built before being configured. Please call IEndpointBuilder.Build() or .Create() prior to configuration.");

            
            ConfigureEndpointDescription(endpoint.Metadata);
            ConfigureEndpointManager(endpoint);
            ConfigureProfiler(endpoint);
            ConfigureMessageHandlerAssemblies(endpoint);
            ConfigureMessageAssemblies(endpoint);
            ConfigureMessageFilters(endpoint);
            ConfigureTransport<TransportType>(endpoint);
        }

        protected virtual void ConfigureEndpointManager(IEndpointBuilder endpoint)
        {
            if (!String.IsNullOrEmpty(MonitorServiceAddress))
            {
                string monitorAddress = MonitorServiceAddress;
                TimeSpan reportInterval = StatusReportInterval;
                TimeSpan gracePeriod = StatusReportGracePeriod;

                var mgr = endpoint.ConfigureEndpointManager(monitorAddress, reportInterval, gracePeriod);
              
                if (StatusProbes != null && StatusProbes.Count > 0)
                {
                    for (int i = 0; i < StatusProbes.Count; i++)
                    {
                        var newProbe = StatusProbes[i].CreateProbeFromConfiguration();
                        if (newProbe != null)
                            mgr.EndpointStatusProbes.Add(newProbe);
                    }
                }
            }
        }

        protected virtual void ConfigureProfiler(IEndpointBuilder endpoint)
        {
            if (Profiler != null)
            {
                endpoint.ConfigureProfiler(Profiler.Enabled, Profiler.ProfilePipelineComponents,Profiler.PipelineDirection);
            }
        }

        protected virtual void ConfigureMessageHandlerAssemblies(IEndpointBuilder endpoint)
        {
            if (MessageHandlerAssemblies != null && MessageHandlerAssemblies.Count > 0)
            {
                for (int i=0;i<MessageHandlerAssemblies.Count;i++)
                {
                    Assembly asm = MessageHandlerAssemblies[i].Assembly;
                    if (asm != null)
                        endpoint.MessageHandlers.RegisterMessageHandlerAssembly(asm);
                }
            }
        }

        protected virtual void ConfigureMessageAssemblies(IEndpointBuilder endpoint)
        {
            if (MessageAssemblies != null && MessageAssemblies.Count > 0)
            {
                for (int i = 0; i < MessageAssemblies.Count; i++)
                {
                    Assembly asm = MessageAssemblies[i].Assembly;
                    if (asm != null)
                    {
                        string destinationAddress = MessageAssemblies[i].DestinationAddress;
                        if (String.IsNullOrEmpty(destinationAddress)) destinationAddress = null;


                        endpoint.MessageTypes.RegisterMessageAssembly(asm, destinationAddress);
                    }
                }
            }
        }

        protected virtual void ConfigureMessageFilters(IEndpointBuilder endpoint)
        {
            if (MessageFilters != null && MessageFilters.Count > 0)
            {
                for(int i=0;i<MessageFilters.Count;i++)
                {
                    if (MessageFilters[i] != null && MessageFilters[i].Enabled)
                    {
                        MessageFilterConfigurationElement filter = MessageFilters[i];
                        var predicate =
                            new ExpressionMessagePredicate(filter.FilterExpression,
                                                           endpoint.GetService<IMessageExpressionEvaluator>());

                        if (MessageFilters[i].FilterDirection.Contains("send") || filter.FilterDirection.Contains("both"))
                            endpoint.MessageFilters.RegisterMessageFilter(filter.MessageType,predicate, PipelineDirection.Send);
                        if (MessageFilters[i].FilterDirection.Contains("receive") || filter.FilterDirection.Contains("both"))
                            endpoint.MessageFilters.RegisterMessageFilter(filter.MessageType,predicate, PipelineDirection.Receive);
                    }
                }
            }
        }

        protected virtual TransportType ConfigureTransport<TransportType>(IEndpointBuilder endpoint)
            where TransportType : class, ITransport
        {
            var transport = (TransportType)endpoint.Endpoint.LocalServices.GetService<ITransport>();
            if (transport != null)
            {
                if (NumberOfWorkerThreads > 0)
                    transport.NumberOfWorkerThreads = NumberOfWorkerThreads;
                if (MaxRetries > 0)
                    transport.MaxRetries = MaxRetries;
            }
            return transport;
        }

        public static void ConfigureEndpoint<TransportType>(IEndpointBuilder endpoint)
            where TransportType : class, ITransport
        {
            if (endpoint == null)
                throw new ArgumentNullException("endpoint");

            EndpointConfiguration config = GetConfiguration();
            if (config != null)
            {
                config.Configure<TransportType>(endpoint);
            }
        }

        public static EndpointBuilderType ConfigureEndpoint<EndpointBuilderType,TransportType>()
            where EndpointBuilderType : class, IEndpointBuilder, new()
            where TransportType : class, ITransport
        {
            return ConfigureEndpoint<EndpointBuilderType,TransportType>(null);
        }

        public static EndpointBuilderType ConfigureEndpoint<EndpointBuilderType,TransportType>(IObjectBuilder IoC)
            where EndpointBuilderType : class, IEndpointBuilder, new()
            where TransportType : class, ITransport
        {
            var builder = (EndpointBuilderType)Activator.CreateInstance(typeof (EndpointBuilderType));
            builder.CreateEndpoint(IoC);
            ConfigureEndpoint<TransportType>(builder);
            return builder;
        }

        public static IMessagingEndpoint CreateConfiguredEndpoint<EndpointBuilderType,TransportType>(params Assembly[] messageHandlerAssemblies)
            where EndpointBuilderType:class,IEndpointBuilder, new()
            where TransportType : class, ITransport
        {
            return CreateConfiguredEndpoint<EndpointBuilderType,TransportType>((IObjectBuilder)null);
        }

        public static IMessagingEndpoint CreateConfiguredEndpoint<EndpointBuilderType,TransportType>(IObjectBuilder IoC,params Assembly[] messageHandlerAssemblies)
            where EndpointBuilderType: class,IEndpointBuilder, new()
            where TransportType : class, ITransport
        {
            IEndpointBuilder builder =  ConfigureEndpoint<EndpointBuilderType,TransportType>(IoC);
            if (messageHandlerAssemblies != null)
                foreach (Assembly asm in messageHandlerAssemblies)
                    builder.MessageHandlers.RegisterMessageHandlerAssembly(asm);
            return builder.Endpoint;
        }

        public static EndpointConfiguration GetConfiguration()
        {
            return GetConfiguration("endpoint");
        }

        public static EndpointConfiguration GetConfiguration(string sectionName)
        {
            return ConfigurationManager.GetSection(sectionName) as EndpointConfiguration;
        }
    }
}